#include "Parser.h"

#include <iostream>
#include <fstream>
#include <string>
#include <vector>

#include "lib.h"

using namespace std;

DebugLevelType Parser::elementaire( BLANK ),
  Parser::fonction( BLANK );

/**
 * Constructeur par défaut, construit le Parser sans initialiser le
 * nom du fichier à lire.
 */
Parser::Parser()
  : nomFichier(""), donnees(""), taille(0), numeroElement( 0 ),
    donneesLues( false ), index( 0 )
{
  Parents.push ( -1 );
}

/**
 * Constructeur permettant d'initialiser directement le nom du
 * fichier à lire.
 *
 * @param nom_fichier nom du fichier à lire
 */
Parser::Parser(const string& nom_fichier)
  : nomFichier( nom_fichier ), donnees(""), taille(0), numeroElement( 0 ),
    donneesLues( false ), index( 0 )
{
  Parents.push ( -1 );
}

/**
 * Destructeur, peut afficher la structure de l'arbre si la valeur
 * de Parser::elementaire est fixée à \e INFO dans le fichier
 * CONFIG.
 */
Parser::~Parser()
{
  Debug::empileNiveau( &elementaire );

  string indentation;
  vector< string > attrib;

  info() << "Affichage : " << ordre.size() << endreq;

  for (unsigned int i (0), j; i < numeroElement; i++)
    {
      indentation = "";
      for (j = 0; j < ordre[i] -> niveau(); j++)
	indentation += "  ";
      info() << indentation << ordre[i] -> balise() << " - \'"
	     << ordre[i] -> donnees() << "\'";
      if ( ordre[i] -> aAttributs() )
	{
	  info() << " (";
	  ordre[i] -> afficheAttributs( info() );
	  info() << ')';
	}
      if ( ordre[i] -> parent() != -1 )
	info() << " - parent "
	       << ordre[ ordre[i] -> parent() ] -> balise() << endreq;
      else
	info() << " - racine" << endreq;
    }

  Debug::depileNiveau();//dout.setLevel( etat_precedent );
}

/**
 * Permet de déterminer le nom du fichier d'entrée, si le
 * constructeur par défaut a été utilisé pour la création de
 * l'instance.
 */
void Parser::setNomFichier(const string& nom_fichier)
{
  nomFichier = nom_fichier;
}

/**
 * Charge le contenu du fichier dans la structure de donnée interne
 * (le fichier est mis dans une string, sans retours à la ligne).
 *
 * @return StatusCode \e true si tout s'est bien déroulé, valeur()
 * donne le nombre de lignes. Dans le cas où un problème apparaît,
 * message() est aussi rempli.
 */
StatusCode Parser::chargerFichier()
{
  ifstream fichier;

  fichier.open( nomFichier.c_str() );

  if ( fichier.fail() )
    {
      error() << "Impossible de lire le fichier " << nomFichier << endreq;
      return StatusCode( false, 0, 0, "Lecture des données impossible" );
    }

  string ligne;
  unsigned int compteur(0);

  while ( !fichier.eof() )
    {
      getline( fichier, ligne );
      donnees += ligne;
      compteur += 1;
    }

  taille = donnees.size();

  donneesLues = true;

  return StatusCode( true, compteur );
}

/**
 * Méthode qui interprète le XML. On procède de manière récursive : si
 * la balise s'ouvre puis se ferme, on va écrire le champ de donnée,
 * ainsi que le "numéro" du champ, qui permet ensuite de reproduire la
 * structure du fichier. Si la balise ne contient pas de données mais
 * d'autres balises, elle est inscrite dans la structure, mais aucune
 * donnée ne lui est associée.\n
 *
 * Les balises sont stockées dans un stack, afin de s'assurer que
 * chaque tag ouvert est bien refermé en respectant la syntaxe XML\n
 *
 * La lecture s'arrête lorsque le stack est vide : c'est à dire
 * lorsque toutes les balises ouvrantes ont été fermées.
 *
 * @return StatusCode \e true si la lecture de l'ElementDOM s'est
 * effectuée sans problème, \e false dans le cas contraire. Un message
 * peut être associé.
 */
StatusCode Parser::lireDocument()
{
  Debug::empileNiveau( &fonction );
  //DebugLevelType etat_precedent( dout.getLevel() );
  //dout.setLevel( fonction );
  func() << "lireDocument -> début" << endreq;

  StatusCode sc;

  if ( ! donneesLues )
    sc = chargerFichier();

  if ( !sc )
    {
      error() << sc.message() << endreq;
      return StatusCode( false, 0, 0, "Impossible de lire le fichier" );
    }

  /*
   * Première étape : trouver une balise ouvrante.
   */

  string balise;
  char o_f;

  trouverOuvrante();

  sc = litBalise();

  balise = sc.message();
  o_f = static_cast<char>( sc.valeur() );

  trouverFermante();

  ElementDOM elDom( isolerAttributs( balise ) );
  
  if ( o_f == 'o' )
    {
      if ( balise[0] == '?' )
	{
	  linfo() << "On saute le header" << endreq;
	  return lireDocument();
	}
      else if ( balise[0] == '!' )
	{
	  linfo() << "On saute une partie commentée" << endl;
	  skipComment();
	  return lireDocument();
	}
      else
	{
	  linfo() << "On ouvre " << balise << endreq;
	  Pile.push( balise );
	}
    }
  else
    {
      if ( Pile.top() != balise )
	{
	  error() << "Balise fermante trouvée alors qu'une ouvrante "
		  << "était attendue" << endreq;
	  return StatusCode( false, 0, 20, "Pas de balise ouvrante trouvée" );
	}
      else
	{
	  linfo() << "On ferme " << balise << endreq;
	  if ( Informations[ Pile.top() ].numero() ==
	       static_cast<unsigned int>(Parents.top()) )
	    Parents.pop();
	  Pile.pop();
	  if ( Pile.empty() )
	    {
	      map< string, ElementDOM >::const_iterator It,
		Begin( Informations.begin() ),
		End( Informations.end() );
	      
	      ordre.resize( numeroElement );
	      
	      for (It = Begin; It != End; It ++)
		{
		  ordre[ It -> second.numero() ] = &(It -> second);
		}
	      
	      func() << "lireDocument -> fin" << endreq;
	      Debug::depileNiveau();//dout.setLevel( etat_precedent );
	      return StatusCode( true, 0, 0, "Lecture terminée" );
	    }
	  return lireDocument();
	}
    }

  sc = verifierContenuFutur( balise );

  elDom . setNumero( numeroElement++ );

  if ( sc )
    {
      // On a trouvé des données
      while ( Informations.find( balise ) != Informations.end() )
	balise += '+';

      int i(index);
      string valeur("");
      while ( donnees[i] != '<' )
	{
	  valeur += donnees[i];
	  i++;
	}
      debug() << "Valeur : \'" << valeur << "\'" << endreq;
      wipeBlank( valeur );
      elDom . setDonnees( valeur );
      elDom . setParent( Parents.top() );
      elDom . setADonnees( true );
      Informations[ balise ] = elDom;
    }
  else if ( balise[ balise.size() - 1 ] == '/'  )
    {
      balise = elDom . balise();
      elDom . setParent( Parents.top() );
      Informations[ balise ] = elDom;
      Pile.pop();
    }
  else
    {
      // On se trouve devant une balise qui contient des balises
      while ( Informations.find( balise ) != Informations.end() )
	balise += '+';

      elDom . setParent( Parents.top() );

      Parents.push( elDom.numero() );
      
      Informations[ balise ] = elDom;
    }

  if ( Pile.size() != 0 )
    {
      func() << "lireDocument -> fin" << endreq;
      Debug::depileNiveau();//dout.setLevel( etat_precedent );
      return lireDocument();
    }

  func() << "lireDocument -> fin" << endreq;
  Debug::depileNiveau();//dout.setLevel( etat_precedent );
  return StatusCode( true );
}

/**
 * Permet de fixer les deux niveaux de sorties (elementaire et
 * fonction).
 *
 * @param lequel 0 pour Parser::elementaire, 1 pour Parser::fonction
 * @param lvl niveau d'affichage
 */
void Parser::setLevel(const DebugLevelType& lvl, const unsigned int& lequel)
{
  switch( lequel )
    {
    case 0:
      elementaire = lvl;
      break;
    case 1:
      fonction = lvl;
      break;
    default:
      warning() << "Tentative de régler un nveau de sortie inexistant"
		<< endreq;
    }
}

/**
 * Permet de récupérer les informations lues, sous forme de
 * map<string,\ string>.
 *
 * @param infosLues map<string,\ string>, qui est remplie par la
 * méthode.
 * @return StatusCode, false si le conteneur est vide.
 */
StatusCode Parser::getInformations(map< string, string >& infosLues) const
{
  Debug::empileNiveau( &fonction );
  func() << "getInformations -> début" << endreq;

  map< string, ElementDOM >::const_iterator It, Begin( Informations.begin() ),
    End( Informations.end() );

  for (It = Begin; It != End; It++)
    {
      if ( It -> second . aDonnees() )
	infosLues[ It -> first ] = It -> second . donnees();
    }

  func() << "getInformations -> fin" << endreq;
  Debug::depileNiveau();
  return StatusCode( !(Begin == End) );
}

/**
 * Permet de récupérer l'ordre des balise, afin de recomposer la
 * structure du fichier.
 *
 * @param structLue conteneur où est récupéré l'ordre.
 * @return StatusCode, false si le conteneur est vide.
 */
StatusCode Parser::getStructureFichier(vector< const ElementDOM* >& structLue)
  const
{
  Debug::empileNiveau( &fonction );
  func() << "getStructureFichier -> début" << endreq;

  structLue.assign( ordre.begin(), ordre.end() );
  
  func() << "getStructureFichier -> fin" << endreq;
  Debug::depileNiveau();
  return StatusCode( !(ordre.empty()) );
}

/**
 * Lit le document et s'arrête à la première balise ouvrante trouvée.
 *
 * @return StatusCode, \e true si une balise ouvrante a bien été
 * trouvée, auquel cas l'index est donné par valeur(). Si on a pas
 * trouvé de balise ouvrante, on renvoie \e false, le code d'erreur 10
 * et un message.
 */
StatusCode Parser::trouverOuvrante()
{
  Debug::empileNiveau( &elementaire );
  int ret(-1); 
  /*
   * La valeur -1 est assignee au cas ou on ne rencontre plus de <
   */
  for(unsigned int i(index); i < taille; i++)
    /*
     * On commence avec la valeur donnee par l'index, derniere position
     * lue par l'index
     */
    {
      if(donnees[i]=='<')
	{
	  ret=i;
	  index=i+1; 
	  /*
	   * On met a jour l'index de lecture ssi elle trouve un <
	   */
	  break;
	}
    }
  /* 
   * Si la methode ne trouve plus de < elle ne met pas a jour l index de
   * lecture
   */
  if(ret==-1) //erreur
    {
      error() << "Début de balise non trouvée, Parser::trouverOuvrante"
	      << endreq;
      return StatusCode( false, 0, 10, "Début de balise non trouvée" );
    }
  Debug::depileNiveau();
  return StatusCode( true, ret );
}

/**
 * Lit le document et s'arrête à la première balise fermante trouvée.
 *
 * @return StatusCode, \e true si une balise fermante a bien été
 * trouvée, auquel cas l'index est donné par valeur(). Si on a pas
 * trouvé de balise fermante, on renvoie \e false, le code d'erreur 11
 * et un message.
 */
StatusCode Parser::trouverFermante ()
{
  Debug::empileNiveau( &elementaire );
  int ret(-1); 
  /*
   * La valeur -1 est assignee au cas ou on ne rencontre plus de /
   */
  for(unsigned int i(index);i<taille;i++)
    /*
     * On commence avec la valeur donnee par l'index, derniere position lue
     * par l'index
     */
    {
      if(donnees[i]=='>')
	{
	  ret=i;
	  index++;
	  /*
	   * On met a jour l'index de lecture
	   */
	  break;
	}
    }
  if(ret==-1) //erreur
    {
      error() << "Fin de balise non trouvée dans Parser::trouverFermante"
	      << endreq;
      return StatusCode( false, 0, 11, "Fin de balise non trouvée" );
    }
  Debug::depileNiveau();
  return StatusCode( true, ret );
}

/**************************************************************************//**
 *
 * Permet de s'affranchir des ' ' entre deux balises, deux informations, ...
 *
 * FUNC  permet d'afficher les messages de début et de fin
 *
 *****************************************************************************/
void Parser::sauterBlanc()
{
  Debug::empileNiveau( &elementaire );
  //DebugLevelType etat_precedent(dout.getLevel());
  //dout.setLevel(elementaire);
  func() << "sauterBlanc -> début" << endreq;
  while ( (donnees[index]==32) || (donnees[index]==10))
    {
      index++;
    }
  func() << "sauterBlanc -> fin" << endreq;
  Debug::depileNiveau();//dout.setLevel(etat_precedent);
}

/**************************************************************************//**
 *
 * Fait disparaître les ' ' à la fin d'une string lors de la récupération
 * depuis un fichier .xml
 *
 *****************************************************************************/
void Parser::wipeBlank(string& avecEspaces)
{
  Debug::empileNiveau( &fonction );
  //DebugLevelType etat_precedent(dout.getLevel());
  //dout.setLevel( fonction );
  func() << "wipeBlank -> début" << endreq;

  debug() << "\'" << avecEspaces << "\' -> ";

  if ( avecEspaces != "" )
    {
      unsigned int i( avecEspaces.size() - 1 ), j(0);

      bool seulEspaces( true );

      for (j = 0; j <= i; j++)
	seulEspaces &= ( avecEspaces[j] == ' ' );

      if ( seulEspaces )
	{
	  debug() << "(uniquement espaces) ";
	  avecEspaces = "";
	}
      else
	{
	  j = 0;
	  while ( avecEspaces[i] == ' ' )
	    i--;
	  while ( avecEspaces[j] == ' ' )
	    j++;
	  string tmp("");
	  for (; j <= i; j++)
	    {
	      tmp += avecEspaces[j];
	    }
	  avecEspaces = tmp;
	}
    }
  
  debug() << "\'" << avecEspaces << "\'" << endreq;

  func() << "wipeBlank -> fin" << endreq;
  Debug::depileNiveau();//dout.setLevel(etat_precedent);
}

/**************************************************************************//**
 *
 * Lit le contenu de la balise
 *
 * FUNC  permet d'afficher les messages de début et de fin
 *
 *****************************************************************************/
StatusCode Parser::litBalise()
{
  Debug::empileNiveau( &elementaire );
  //DebugLevelType etat_precedent(dout.getLevel());
  //dout.setLevel(elementaire);
  char o_f;
  func() << "litBalise -> début" << endreq;
  if(donnees[index]=='/') 
    /*
     * On a affaire a une balise fermante
     */
    {
      index += 1;
      o_f = 'f';
      /*
       * On oublie pas d'avancer l'index
       * C'est une fermante
       */
    }
  else 
    {
      o_f = 'o';
      /*
       * C'est une ouvrante
       */
    }
  
  string out("");
  while( donnees[index] != '>' )
    {
      out += donnees[index];
      index++;
    }

  func() << "litBalise -> fin" << endreq;
  Debug::depileNiveau();//dout.setLevel(etat_precedent);
  return StatusCode( true, static_cast<int>(o_f), 0, out );
}

/**************************************************************************//**
 *
 * Permet de sauter un commentaire
 *
 * FUNC  permet d'afficher les messages de début et de fin
 *
 *****************************************************************************/
StatusCode Parser::skipComment()
{
  Debug::empileNiveau( &elementaire );
  //DebugLevelType etat_precedent(dout.getLevel());
  //dout.setLevel(elementaire);
  func() << "skipComment -> début" << endreq;
  int ancienne_pos_index(index);
  /*
   * Memorise la position initiale de l'index
   */
  int temp( trouverOuvrante() );
  /*
   * La position de l'index est modifiee ici
   */
  if (temp==-1) index=ancienne_pos_index;
  /*
   * Plus de commentaire possible car pas de <
   */
  else
    /*
     * Il a trouve un <
     */
    {
      sauterBlanc();
      /*
       * Il saute les blancs et pointe le premier caractere apres <
       */
      if(donnees[index]=='!')
	/*
	 * On a affaire a un commentaire car commence par !
	 */
	{
	  int blabla(trouverFermante());
	  /*
	   * Cherche la fin du commentaire (il cherche >) et met à jour
	   l'index de lecture, job terminé
	  */
	  if(blabla==-1)
	    /*
	     * Cas ou le commentaire n a pas de fin -> Erreur !
	     */
	    {
	      error() << "Syntaxe de commentaire incorrecte dans Parser::skipComment()" << endreq;
	      return StatusCode( false, 0, 12, "Erreur de syntaxe dans le commentaire" );
	    }
	}
      else
	{
	  /*
	   * Si le caractère n'est pas '!' il remet l'index ou il était,
	   * il n a pas eu affaire à un commentaire
	   */
	  index=ancienne_pos_index;
	}
    }
  func() << "skipComment -> fin" << endreq;
  /*
   * Au final la position de l'index est modifiée que si un commentaire
   * a été trouvé et dans un tel cas on a "passé" le commentaire
   */
  Debug::depileNiveau();//dout.setLevel(etat_precedent);
  return StatusCode( true );
}

/**************************************************************************//**
 *
 * Gestion de la bonne structure du fichier .xml
 * Cherche le contenu de la balise suivante et le compare à la string b
 *
 *****************************************************************************/
StatusCode Parser::verifierContenuFutur(const string& future)
{
  Debug::empileNiveau( &elementaire );
  //DebugLevelType etat_precedent(dout.getLevel());
  //dout.setLevel(elementaire);
  int mem_pos(index);
  skipComment();
  trouverOuvrante();
  StatusCode sc( litBalise() );
  index = mem_pos;
  Debug::depileNiveau();//dout.setLevel(etat_precedent);
  if ( (sc.message() == future) && (static_cast<char>(sc.valeur()) == 'f'))
    return StatusCode( true );
  else
    return StatusCode( false, 0, 0, "La balise suivante n'est pas celle attendue" );
}

/**
 * Lit la balise et construit un ElementDOM avec les éventuels
 * attributs présents. Est capable de lire une balise du type
 * \<contenu/\>
 */
ElementDOM Parser::isolerAttributs(string& balise)
{
  Debug::empileNiveau( &fonction );
  func() << "isolerAttributs -> début" << endreq;

  string tmpBal(""), tmpAttr, tmpVal;

  size_t taille( balise.size() ), i(0);

  bool baliseVide( balise[ taille - 1 ] == '/' );

  if ( baliseVide )
    balise = balise.substr(0, taille - 1);

  while ( balise[i] != ' ' && i < taille )
    {
      tmpBal += balise[i];
      i++;
    }

  ElementDOM dom( tmpBal, Pile.size() );

  while ( balise[i] == ' ' && i < taille )
    i++;

  for (; i < taille; i++)
    {
      tmpAttr = "";
      tmpVal  = "";
      while ( balise[i] != '=' && i < taille )
	{
	  tmpAttr += balise[i];
	  i++;
	}
      while ( balise[i] != '\"' && i < taille )
	i++;
      i++;
      while ( i < taille )
	{
	  if ( balise[i] == '\"' ) break;
	  tmpVal += balise[i];
	  i++;
	}
      dom . ajoutAttribut( tmpAttr, tmpVal );
      while ( balise[i] == ' ' && i < taille )
	i++;
    }

  balise = tmpBal;

  if ( baliseVide )
    balise += '/';

  func() << "isolerAttributs -> fin" << endreq;
  Debug::depileNiveau();
  return dom;
}
